#include "PcscPlugin.h"

#if !defined(WIN32)
#define MAXIMUM_SMARTCARD_READERS 10
#endif

PcscPlugin::PcscPlugin() : Plugin("pcsc")
{
	// Set the default enumerator values
	ErrorDefault = "SCARD_F_UNKNOWN_ERROR";
	ProtocolDefault = "SCARD_PROTOCOL_UNDEFINED";
	StateDefault = "SCARD_UNKNOWN";
}

void PcscPlugin::InitializePlugin( PluginSpMapSp plugins, INameResolutorSp nameResolutor )
{
	Plugin::InitializePlugin(plugins, nameResolutor);

	// Get useful references to type descriptors
	BufferTd = descriptorsRepository->GetVectorTypeDescriptor("pcsc.Buffer");
	DispositionTd = descriptorsRepository->GetEnumTypeDescriptor("pcsc.Disposition");
	ErrorTd = descriptorsRepository->GetEnumTypeDescriptor("pcsc.Error");
	ProtocolTd = descriptorsRepository->GetEnumTypeDescriptor("pcsc.Protocol");
	ScopeTd = descriptorsRepository->GetEnumTypeDescriptor("pcsc.Scope");
	SharemodeTd = descriptorsRepository->GetEnumTypeDescriptor("pcsc.Sharemode");
	StateTd = descriptorsRepository->GetEnumTypeDescriptor("pcsc.State");
	VstringTd = descriptorsRepository->GetVectorTypeDescriptor("pcsc.Vstring");
}

void PcscPlugin::ExecuteCommand(CommandSp cmd)
{
	Plugin::ExecuteCommand(cmd);

	// Call the right command handler
	if ( commandName == "Connect" )	Pcsc_Connect();
	else if ( commandName == "Disconnect" )	Pcsc_Disconnect();
	else if ( commandName == "EstablishContext" )	Pcsc_EstablishContext();
	else if ( commandName == "ListReaders" )	Pcsc_ListReaders();
	else if ( commandName == "ReleaseContext" )	Pcsc_ReleaseContext();
	else if ( commandName == "Status" )	Pcsc_Status();
	else if ( commandName == "Transmit" )	Pcsc_Transmit();
	else throw SPluginException("Error: command not implemented by plugin.");

}

void PcscPlugin::Pcsc_Connect()
{
	// Input preparation   
	SNumberSp context  = inputs->Get<SNumber>("context");
	SStringSp reader  = inputs->Get<SString>("reader");
	SEnumSp sharemode  = inputs->Get<SEnum>("sharemode");
	SEnumSp protocol  = inputs->Get<SEnum>("protocol");

	// Input preparation
	SCARDCONTEXT scardcontext = (SCARDCONTEXT) context->Get();
	string readerStr = reader->Get();
	DWORD sharemodeWord = (DWORD) sharemode->GetNumericValue();
	DWORD protocolWord = (DWORD) protocol->GetNumericValue();

	DWORD activeProtocol = 0;
	SCARDHANDLE connection = 0;

	// Command execution

#if defined(WIN32)
	LONG res = SCardConnectA(scardcontext, readerStr.c_str(), sharemodeWord, protocolWord, &connection, &activeProtocol);
#else
	LONG res = SCardConnect(scardcontext, readerStr.c_str(), sharemodeWord, protocolWord, &connection, &activeProtocol);
#endif

	if ( res == SCARD_S_SUCCESS )
	{
		outputs->Set("connection", SNumber::Create((unsigned long)connection) );
		SetProtocol("protocol", activeProtocol);
	}

	// Setting outputs
	SetError("res", res);
}

void PcscPlugin::Pcsc_Disconnect()
{
	// Input preparation   
	SEnumSp disposition  = inputs->Get<SEnum>("disposition");
	SNumberSp connection  = inputs->Get<SNumber>("connection");

	SCARDHANDLE connectionHandle = (SCARDHANDLE) connection->Get();
	DWORD dispositionWord = (DWORD) disposition->GetNumericValue();

	// Command execution
	LONG res = SCardDisconnect(connectionHandle, dispositionWord);

	// Setting outputs
	SetError("res", res);
}

void PcscPlugin::Pcsc_EstablishContext()
{
	// Input preparation   
	SEnumSp scope  = inputs->Get<SEnum>("scope");

	string scopeStr = scope->Get();

	SCARDCONTEXT scardcontext = 0;
	LONG scard_scope = SCARD_SCOPE_SYSTEM;
	if ( scopeStr == "SCARD_SCOPE_USER") scard_scope = SCARD_SCOPE_USER;

	// Command execution
	LONG res = SCardEstablishContext(scard_scope, NULL, NULL, &scardcontext);

	// Setting outputs
	SetError( "res", res );
	outputs->Set("context", SNumber::Create( (long) scardcontext) );
}

void PcscPlugin::Pcsc_ListReaders()
{
	// Input preparation   
	SNumberSp context  = inputs->Get<SNumber>("context");

	SCARDCONTEXT scardcontext = (SCARDCONTEXT) context->Get();

	// Command execution
	DWORD readersLength = 0;
	LONG res = SCardListReaders(scardcontext, NULL, NULL, &readersLength);

	LPSTR readers = new char[readersLength];

#if defined(WIN32)
	res = SCardListReadersA(scardcontext, NULL, readers, &readersLength);
#else
	res = SCardListReaders(scardcontext, NULL, readers, &readersLength);
#endif

	// Setting outputs
	SVectorSp vreaders = SVector::Create(VstringTd);

	if ( res == SCARD_S_SUCCESS )
	{
		LPSTR p = readers;

		for ( int i = 0; i < MAXIMUM_SMARTCARD_READERS; ++i )
		{
			if ( *p == 0 )
			{
				break;
			}

			vreaders->AddItem( SString::Create( string(p) ) );

			p += strlen(p) + 1;
		}
	}

	delete readers;

	SetError( "res", res );
	outputs->Set("readers", vreaders);
}

void PcscPlugin::Pcsc_ReleaseContext()
{
	// Input preparation   
	SNumberSp context  = inputs->Get<SNumber>("context");

	SCARDCONTEXT scardcontext = (SCARDCONTEXT) context->Get();

	// Command execution
	LONG res = SCardReleaseContext(scardcontext);

	// Setting outputs
	SetError( "res", res );
}

void PcscPlugin::Pcsc_Status()
{
	// Input preparation   
	SNumberSp connection  = inputs->Get<SNumber>("connection");

	SCARDHANDLE connectionHandle = (SCARDHANDLE) connection->Get();
	DWORD readersLength = 256;
	LPSTR readers = new char[readersLength];
	DWORD readerState;
	DWORD readerProtocol;
	BYTE atr[33];
	DWORD atrLength = 33;

	// Command execution

#if defined(WIN32)
	LONG res = SCardStatusA(connectionHandle, readers, &readersLength, &readerState, &readerProtocol, atr, &atrLength);
#else
	LONG res = SCardStatus(connectionHandle, readers, &readersLength, &readerState, &readerProtocol, atr, &atrLength);
#endif

	// Setting outputs
	SetError( "res", res );

	string reader = string(readers);
	outputs->Set("reader", SString::Create(reader));
	
	SetState( "state", readerState );
	SetProtocol( "protocol", readerProtocol );

	SVectorSp vAtr = SVector::Create(BufferTd);
	for(int i=0; i< (int) atrLength; ++i)
	{
		vAtr->AddItem( SByte::Create( (unsigned char) atr[i] ) );
	}
	outputs->Set("atr", vAtr);
}

void PcscPlugin::Pcsc_Transmit()
{
	// Input preparation   
	SVectorSp data  = inputs->Get<SVector>("data");
	SNumberSp connection  = inputs->Get<SNumber>("connection");
	SEnumSp protocol  = inputs->Get<SEnum>("protocol");

	SCARDHANDLE connectionHandle = (SCARDHANDLE) connection->Get();
	DWORD readerProtocol = (DWORD) protocol->GetNumericValue();

	DWORD sendBufferLength = (DWORD) data->GetLength();
	BYTE* sendBuffer = new BYTE[sendBufferLength];

	for(int i=0; i < (int) sendBufferLength; ++i)
	{
		sendBuffer[i] = (BYTE) data->Get<SByte>(i)->Get();
	}

	// Struct used to send information on PCI (Protocol Control Information)
	SCARD_IO_REQUEST pioSendPci;	
	pioSendPci.dwProtocol = readerProtocol;        // protocol to use
	pioSendPci.cbPciLength = sizeof(pioSendPci);   // length in byte of the structure

	DWORD receiveBufferLength = 256;
	BYTE receiveBuffer[256];
	memset( receiveBuffer, 0, sizeof(receiveBuffer));
	LONG res;

	if ( readerProtocol == SCARD_PROTOCOL_T0)
	{
		res = SCardTransmit(connectionHandle, SCARD_PCI_T0, sendBuffer, sendBufferLength, NULL, receiveBuffer, &receiveBufferLength);	
	}
	else if ( readerProtocol == SCARD_PROTOCOL_T1)
	{
		res = SCardTransmit(connectionHandle, SCARD_PCI_T1, sendBuffer, sendBufferLength, NULL, receiveBuffer, &receiveBufferLength);	
	}
	else
	{
		res = SCardTransmit(connectionHandle, &pioSendPci, sendBuffer, sendBufferLength, NULL, receiveBuffer, &receiveBufferLength);	
	}

	delete sendBuffer;

	// Settings outputs
	SetError( "res", res );

	SVectorSp rb = SVector::Create(BufferTd);
	for(int i = 0; i < (int) receiveBufferLength; ++i)
	{
		rb->AddItem( SByte::Create( (unsigned char) receiveBuffer[i] ) );
	}
	outputs->Set("receiveBuffer", rb);
}

/*****************************************************/
/*           Enumerator support methods              */
/*****************************************************/

void PcscPlugin::SetError( string outputParameterName, string enumValue )
{
	if ( ErrorTd->IsDefined(enumValue) )
	{
		outputs->Set(outputParameterName, SEnum::Create(ErrorTd, enumValue) );
	}
	else
	{
		outputs->Set(outputParameterName, SEnum::Create(ErrorTd, ErrorDefault) );
	}
}
void PcscPlugin::SetError( string outputParameterName, unsigned long enumNumericValue )
{
	string enumValue = ErrorDefault;

	if ( enumNumericValue == SCARD_S_SUCCESS)	enumValue = "SCARD_S_SUCCESS";
	if ( enumNumericValue == SCARD_F_INTERNAL_ERROR)	enumValue = "SCARD_F_INTERNAL_ERROR";
	if ( enumNumericValue == SCARD_E_CANCELLED)	enumValue = "SCARD_E_CANCELLED";
	if ( enumNumericValue == SCARD_E_INVALID_HANDLE)	enumValue = "SCARD_E_INVALID_HANDLE";
	if ( enumNumericValue == SCARD_E_INVALID_PARAMETER)	enumValue = "SCARD_E_INVALID_PARAMETER";
	if ( enumNumericValue == SCARD_E_INVALID_TARGET)	enumValue = "SCARD_E_INVALID_TARGET";
	if ( enumNumericValue == SCARD_E_NO_MEMORY)	enumValue = "SCARD_E_NO_MEMORY";
	if ( enumNumericValue == SCARD_F_WAITED_TOO_LONG)	enumValue = "SCARD_F_WAITED_TOO_LONG";
	if ( enumNumericValue == SCARD_E_INSUFFICIENT_BUFFER)	enumValue = "SCARD_E_INSUFFICIENT_BUFFER";
	if ( enumNumericValue == SCARD_E_UNKNOWN_READER)	enumValue = "SCARD_E_UNKNOWN_READER";
	if ( enumNumericValue == SCARD_E_TIMEOUT)	enumValue = "SCARD_E_TIMEOUT";
	if ( enumNumericValue == SCARD_E_SHARING_VIOLATION)	enumValue = "SCARD_E_SHARING_VIOLATION";
	if ( enumNumericValue == SCARD_E_NO_SMARTCARD)	enumValue = "SCARD_E_NO_SMARTCARD";
	if ( enumNumericValue == SCARD_E_UNKNOWN_CARD)	enumValue = "SCARD_E_UNKNOWN_CARD";
	if ( enumNumericValue == SCARD_E_CANT_DISPOSE)	enumValue = "SCARD_E_CANT_DISPOSE";
	if ( enumNumericValue == SCARD_E_PROTO_MISMATCH)	enumValue = "SCARD_E_PROTO_MISMATCH";
	if ( enumNumericValue == SCARD_E_NOT_READY)	enumValue = "SCARD_E_NOT_READY";
	if ( enumNumericValue == SCARD_E_INVALID_VALUE)	enumValue = "SCARD_E_INVALID_VALUE";
	if ( enumNumericValue == SCARD_E_SYSTEM_CANCELLED)	enumValue = "SCARD_E_SYSTEM_CANCELLED";
	if ( enumNumericValue == SCARD_F_COMM_ERROR)	enumValue = "SCARD_F_COMM_ERROR";
	if ( enumNumericValue == SCARD_F_UNKNOWN_ERROR)	enumValue = "SCARD_F_UNKNOWN_ERROR";
	if ( enumNumericValue == SCARD_E_INVALID_ATR)	enumValue = "SCARD_E_INVALID_ATR";
	if ( enumNumericValue == SCARD_E_NOT_TRANSACTED)	enumValue = "SCARD_E_NOT_TRANSACTED";
	if ( enumNumericValue == SCARD_E_READER_UNAVAILABLE)	enumValue = "SCARD_E_READER_UNAVAILABLE";
	if ( enumNumericValue == SCARD_E_PCI_TOO_SMALL)	enumValue = "SCARD_E_PCI_TOO_SMALL";
	if ( enumNumericValue == SCARD_E_READER_UNSUPPORTED)	enumValue = "SCARD_E_READER_UNSUPPORTED";
	if ( enumNumericValue == SCARD_E_DUPLICATE_READER)	enumValue = "SCARD_E_DUPLICATE_READER";
	if ( enumNumericValue == SCARD_E_CARD_UNSUPPORTED)	enumValue = "SCARD_E_CARD_UNSUPPORTED";
	if ( enumNumericValue == SCARD_E_NO_SERVICE)	enumValue = "SCARD_E_NO_SERVICE";
	if ( enumNumericValue == SCARD_E_SERVICE_STOPPED)	enumValue = "SCARD_E_SERVICE_STOPPED";
	if ( enumNumericValue == SCARD_E_UNSUPPORTED_FEATURE)	enumValue = "SCARD_E_UNSUPPORTED_FEATURE";
	if ( enumNumericValue == SCARD_E_NO_READERS_AVAILABLE)	enumValue = "SCARD_E_NO_READERS_AVAILABLE";
	if ( enumNumericValue == SCARD_W_UNSUPPORTED_CARD)	enumValue = "SCARD_W_UNSUPPORTED_CARD";
	if ( enumNumericValue == SCARD_W_UNRESPONSIVE_CARD)	enumValue = "SCARD_W_UNRESPONSIVE_CARD";
	if ( enumNumericValue == SCARD_W_UNPOWERED_CARD)	enumValue = "SCARD_W_UNPOWERED_CARD";
	if ( enumNumericValue == SCARD_W_RESET_CARD)	enumValue = "SCARD_W_RESET_CARD";
	if ( enumNumericValue == SCARD_W_REMOVED_CARD)	enumValue = "SCARD_W_REMOVED_CARD";
	
#if defined(WIN32)

	if ( enumNumericValue == SCARD_P_SHUTDOWN)	enumValue = "SCARD_P_SHUTDOWN";
	if ( enumNumericValue == SCARD_E_UNEXPECTED)	enumValue = "SCARD_E_UNEXPECTED";
	if ( enumNumericValue == SCARD_E_ICC_INSTALLATION)	enumValue = "SCARD_E_ICC_INSTALLATION";
	if ( enumNumericValue == SCARD_E_ICC_CREATEORDER)	enumValue = "SCARD_E_ICC_CREATEORDER";
	if ( enumNumericValue == SCARD_E_DIR_NOT_FOUND)	enumValue = "SCARD_E_DIR_NOT_FOUND";
	if ( enumNumericValue == SCARD_E_FILE_NOT_FOUND)	enumValue = "SCARD_E_FILE_NOT_FOUND";
	if ( enumNumericValue == SCARD_E_NO_DIR)	enumValue = "SCARD_E_NO_DIR";
	if ( enumNumericValue == SCARD_E_NO_FILE)	enumValue = "SCARD_E_NO_FILE";
	if ( enumNumericValue == SCARD_E_NO_ACCESS)	enumValue = "SCARD_E_NO_ACCESS";
	if ( enumNumericValue == SCARD_E_WRITE_TOO_MANY)	enumValue = "SCARD_E_WRITE_TOO_MANY";
	if ( enumNumericValue == SCARD_E_BAD_SEEK)	enumValue = "SCARD_E_BAD_SEEK";
	if ( enumNumericValue == SCARD_E_INVALID_CHV)	enumValue = "SCARD_E_INVALID_CHV";
	if ( enumNumericValue == SCARD_E_UNKNOWN_RES_MNG)	enumValue = "SCARD_E_UNKNOWN_RES_MNG";
	if ( enumNumericValue == SCARD_E_NO_SUCH_CERTIFICATE)	enumValue = "SCARD_E_NO_SUCH_CERTIFICATE";
	if ( enumNumericValue == SCARD_E_CERTIFICATE_UNAVAILABLE)	enumValue = "SCARD_E_CERTIFICATE_UNAVAILABLE";
	if ( enumNumericValue == SCARD_E_COMM_DATA_LOST)	enumValue = "SCARD_E_COMM_DATA_LOST";	
	if ( enumNumericValue == SCARD_E_NO_KEY_CONTAINER)	enumValue = "SCARD_E_NO_KEY_CONTAINER";
	if ( enumNumericValue == SCARD_E_SERVER_TOO_BUSY)	enumValue = "SCARD_E_SERVER_TOO_BUSY";
	if ( enumNumericValue == SCARD_W_SECURITY_VIOLATION)	enumValue = "SCARD_W_SECURITY_VIOLATION";
	if ( enumNumericValue == SCARD_W_WRONG_CHV)	enumValue = "SCARD_W_WRONG_CHV";
	if ( enumNumericValue == SCARD_W_CHV_BLOCKED)	enumValue = "SCARD_W_CHV_BLOCKED";
	if ( enumNumericValue == SCARD_W_EOF)	enumValue = "SCARD_W_EOF";
	if ( enumNumericValue == SCARD_W_CANCELLED_BY_USER)	enumValue = "SCARD_W_CANCELLED_BY_USER";
	if ( enumNumericValue == SCARD_W_CARD_NOT_AUTHENTICATED)	enumValue = "SCARD_W_CARD_NOT_AUTHENTICATED";

#endif

	outputs->Set(outputParameterName, SEnum::Create(ErrorTd, enumValue) );
}
void PcscPlugin::SetProtocol( string outputParameterName, string enumValue )
{
	if ( ProtocolTd->IsDefined(enumValue) )
	{
		outputs->Set(outputParameterName, SEnum::Create(ProtocolTd, enumValue) );
	}
	else
	{
		outputs->Set(outputParameterName, SEnum::Create(ProtocolTd, ProtocolDefault) );
	}
}
void PcscPlugin::SetProtocol( string outputParameterName, unsigned long enumNumericValue )
{
	string enumValue = ProtocolDefault;

	if ( enumNumericValue == SCARD_PROTOCOL_UNDEFINED)	enumValue = "SCARD_PROTOCOL_UNDEFINED";
	if ( enumNumericValue == SCARD_PROTOCOL_T0)	enumValue = "SCARD_PROTOCOL_T0";
	if ( enumNumericValue == SCARD_PROTOCOL_T1)	enumValue = "SCARD_PROTOCOL_T1";
	if ( enumNumericValue == SCARD_PROTOCOL_RAW)	enumValue = "SCARD_PROTOCOL_RAW";

#if defined(WIN32)

	if ( enumNumericValue == SCARD_PROTOCOL_Tx)	enumValue = "SCARD_PROTOCOL_Tx";
	if ( enumNumericValue == SCARD_PROTOCOL_DEFAULT)	enumValue = "SCARD_PROTOCOL_DEFAULT";

#endif

	outputs->Set(outputParameterName, SEnum::Create(ProtocolTd, enumValue) );
}
void PcscPlugin::SetState( string outputParameterName, string enumValue )
{
	if ( StateTd->IsDefined(enumValue) )
	{
		outputs->Set(outputParameterName, SEnum::Create(StateTd, enumValue) );
	}
	else
	{
		outputs->Set(outputParameterName, SEnum::Create(StateTd, StateDefault) );
	}
}
void PcscPlugin::SetState( string outputParameterName, unsigned long enumNumericValue )
{
	string enumValue = StateDefault;

	if ( enumNumericValue == SCARD_UNKNOWN)	enumValue = "SCARD_UNKNOWN";
	if ( enumNumericValue == SCARD_ABSENT)	enumValue = "SCARD_ABSENT";
	if ( enumNumericValue == SCARD_PRESENT)	enumValue = "SCARD_PRESENT";
	if ( enumNumericValue == SCARD_SWALLOWED)	enumValue = "SCARD_SWALLOWED";
	if ( enumNumericValue == SCARD_POWERED)	enumValue = "SCARD_POWERED";
	if ( enumNumericValue == SCARD_NEGOTIABLE)	enumValue = "SCARD_NEGOTIABLE";
	if ( enumNumericValue == SCARD_SPECIFIC)	enumValue = "SCARD_SPECIFIC";

	outputs->Set(outputParameterName, SEnum::Create(StateTd, enumValue) );
}
